/**
 * @author Tobias Boehm
 *
 * 19.05.2011
 */
package org.anddev.andengine.extension.tbsengine.match;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.anddev.andengine.entity.layer.tiled.tmx.util.exception.TMXLoadException;
import org.anddev.andengine.extension.multiplayer.protocol.adt.message.IMessage;
import org.anddev.andengine.extension.multiplayer.protocol.util.MessagePool;
import org.anddev.andengine.extension.tbsengine.faction.AbsBaseFaction;
import org.anddev.andengine.extension.tbsengine.map.AbsMapOverview;
import org.anddev.andengine.extension.tbsengine.match.playerslot.AbsPlayerSlot.SlotState;
import org.anddev.andengine.extension.tbsengine.match.playerslot.BaseServerPlayerSlot;
import org.anddev.andengine.extension.tbsengine.message.client.ClientMatchJoinLeaveRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.AbsClientSlotChangeRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientStartMatchRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientToggleReadyToPlayMessage;
import org.anddev.andengine.extension.tbsengine.message.server.DefaultTBSserverMessageFlags;
import org.anddev.andengine.extension.tbsengine.message.server.ServerMatchPlayerJoinedLeftAnnouncementMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ServerMatchPlayerJoinedLeftAnnouncementMessage.PlayerAnnouncementType;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.AbsServerMatchpreparationJoinRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.AbsServerMatchpreparationSlotChangedAnnouncementMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerMatchpreparationSlotChangeRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerMatchpreprataionStartRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerMatchpreprataionStartRequestReplyMessage.StartReplyReason;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerMatchpreprataionStateChangedAnnouncement;
import org.anddev.andengine.extension.tbsengine.player.BaseHumanPlayer;
import org.anddev.andengine.extension.tbsengine.player.BasePlayer;
import org.anddev.andengine.extension.tbsengine.player.BaseServerHumanPlayer;
import org.anddev.andengine.extension.tbsengine.server.AbsGameServer;
import org.anddev.andengine.extension.tbsengine.team.Team;
import org.xml.sax.SAXException;

import android.content.Context;

/**
 * This is an abstract implementation of a match preparation object.<br>
 * A MatchPreparation object represents the server-sided data of a lounge where 
 * all players gather, choose their teams, colors and factions and discuss how
 * the next match should be configured.
 * <p>
 * Notice:<br>
 * You may wonder why some methods are made abstract although all basic
 * information for the requested task is already available in this implementation.
 * The reason: I would like to enable you to add additional conditions or even 
 * actions to the program flow. 
 *    
 * @author Tobias Boehm
 *
 * @param <GameServerClass> - Your implementation of {@link AbsGameServer}.
 * @param <MatchPreparationClass> - Your implementation of {@link AbsServerMatchPreparation}.
 * @param <BaseFactionClass> - Your implementation of {@link AbsBaseFaction}.
 * @param <ServerMatchJoinRequestReplyMessageClass> - Your implementation of {@link AbsServerMatchpreparationJoinRequestReplyMessage}.
 * @param <ServerPlayerSlotClass> - Your implementation of {@link BaseServerPlayerSlot}.
 * @param <MapOverviewClass> - Your implementation of {@link AbsMapOverview}.
 */
public abstract class AbsServerMatchPreparation<GameServerClass extends AbsGameServer,
										  MatchPreparationClass extends AbsServerMatchPreparation,
										  BaseFactionClass extends AbsBaseFaction,
									      ServerMatchJoinRequestReplyMessageClass extends AbsServerMatchpreparationJoinRequestReplyMessage,
									      ServerPlayerSlotClass extends BaseServerPlayerSlot<BaseFactionClass,?>,
									      MapOverviewClass extends AbsMapOverview> 

										  implements Comparable<AbsServerMatchPreparation>, DefaultTBSserverMessageFlags {

	/**
	 * This enum class represents the possible states a match preparation
	 * object can adopt.
	 * 
	 * @author Tobias Boehm
	 *
	 */
	public static enum MatchPreparationState{PREPARING,STARTING,CANCELED};
	
	
	/**
	 * A reference to the map overview object of the map which will be played.
	 */
	protected final MapOverviewClass mMapOverview;
	
	/**
	 * A reference to the human player who requested to start this match
	 * preparation. 
	 */
	protected final BaseServerHumanPlayer mMatchCreator;
	
	/**
	 * The ID of this match preparation and following match.
	 */
	protected final UUID mMatchID;
	
	/**
	 * A set of players who were banned by the {@link #mMatchCreator} for any
	 * reason.  
	 */
	protected final Set<BaseHumanPlayer> mBannedPlayersSet = Collections.synchronizedSet(new HashSet<BaseHumanPlayer>());
	
	/**
	 * A set of players who indicate to be ready to play. 
	 */
	protected final Set<BasePlayer> mPreparedPlayers = Collections.synchronizedSet(new HashSet<BasePlayer>());
	
	/**
	 * A map of playerIDs -> players who joined this match preparation.
	 */
	protected final Map<UUID,BasePlayer> mJoinedPlayerIDsPlayersMap = Collections.synchronizedMap(new HashMap<UUID,BasePlayer>());
	
	/**
	 * A map of all existing playerSlot IDs -> player slot objects of this
	 * match preparation.
	 */
	protected final Map<Long,ServerPlayerSlotClass> mSlotIDsPlayerSlots = Collections.synchronizedMap(new HashMap<Long,ServerPlayerSlotClass>());
	
	/**
	 * A map of all available team IDs -> teams of this match preparation.
	 */
	protected final Map<Integer,Team> mTeamIDsTeamsMap = Collections.synchronizedMap(new HashMap<Integer,Team>());
	
	/**
	 * A set of all factions which are available to the players.
	 */
	protected final HashSet<BaseFactionClass> mFactions = new HashSet<BaseFactionClass>();
	
	/**
	 * A reference to the game server where this match preparation is stored.
	 */
	protected final GameServerClass mGameServer;
		
	/**
	 * The current state of this match preparation.
	 */
	protected MatchPreparationState mMatchPreparationState;
	
	protected final Context mContext;
	
	
	/**
	 * This is the default constructor for match preparation objects. It creates
	 * a new match preparation based on the chosen map (map overview object),
	 * a match creator and a reference to the game server this match preparation
	 * will be hosted on.
	 * 	 
	 * @param pContext
	 * @param pMatchCreator - The creator of this match (this player has 
	 * 						  extended permissions)
	 * @param pMapOverview - An instance of your implementation of {@link AbsMapOverview}
	 * @param pGameServer - A reference to the game server this match preparation
	 * 						will be hosted on.
	 */
	public AbsServerMatchPreparation(final Context pContext, final BaseServerHumanPlayer pMatchCreator, final MapOverviewClass pMapOverview, final GameServerClass pGameServer){
		
		this.mContext = pContext;
		
		this.mGameServer = pGameServer;
		
		this.mMatchCreator = pMatchCreator;
		
		this.mPreparedPlayers.add(pMatchCreator);
		
		this.mMapOverview = pMapOverview;
		
		this.mMatchID = UUID.randomUUID();
		
		this.mTeamIDsTeamsMap.putAll(this.createTeamsMap(pMapOverview.getPlayerCount()));
		
		this.mFactions.addAll(this.createFactions(pContext));
		
		this.initAdditionalSlotComponents(pMapOverview);
		
		this.mSlotIDsPlayerSlots.putAll(this.createPlayerSlotsMap(pMapOverview.getPlayerCount(), this.mTeamIDsTeamsMap.values(), this.mFactions));
		
		this.mMatchPreparationState = MatchPreparationState.PREPARING;
	}
	
	/**
	 * In this method you have to figure out what kind of slot change the user
	 * requests and if he or she has the permission to do so. Furthermore you
	 * have to set an apposite answer at {@link pReplyMessage}.
	 * <p>
	 * For Team, Faction, Player and State changes you should call
	 * {@link #slotChangeTeam()}, {@link #slotChangeFaction()}, {@link #slotChangePlayer()} 
	 * and {@link #slotChangeState()} and process their return values. Make sure
	 * the match preparation object is in the {@link MatchPreparationState#PREPARING}
	 * state to make changes. Check {@link #mMatchPreparationState} for this 
	 * purpose and throw an {@link IllegalStateException} if that is not the case.
	 * <p>
	 * If you use an extended version of {@link BaseServerPlayerSlot} you may 
	 * implement additional methods according to those mentioned above.
	 *  
	 * @param pClientMessage - The incoming client message.
	 * @param pRequestingPlayer - The player who requests the slot change.
	 * @param pReplyMessage - The reply to this player.
	 */
	public abstract void processMatchPlayerSlotChangeRequest(final AbsClientSlotChangeRequestMessage pClientMessage, final BaseServerHumanPlayer pRequestingPlayer, final ServerMatchpreparationSlotChangeRequestReplyMessage pReplyMessage);
		
	/**
	 * This method should handle an incoming {@link ClientMatchJoinLeaveRequestMessage}.
	 * <p>
	 * Manual:<br>
	 * At first you should check if the match preparation is in {@link MatchPreparationState#PREPARING}
	 * state. Throw an {@link IllegalStateException} if that is not the case. 
	 * If it is in the right state you should check if the requesting player was
	 * banned before ({@link #mBannedPlayersSet}). If you have resolved those
	 * questions you should check if the player wants to join or to leave the
	 * match preparation. Depending on the result you should either call
	 * {@link #joinPlayer(BasePlayer)} or {@link #removePlayer(BaseServerHumanPlayer)}.
	 * Those methods also handle the announcement of the player joining or
	 * leaving the match.
	 * 
	 * @param pReplyMessage - The reply to the requesting player.
	 * @param pJoiningPlayer - The requesting player.
	 * @param pClientMessage - The request message.
	 */
	public abstract void processPlayerJoinOrLeaveReuqest(final ServerMatchJoinRequestReplyMessageClass pReplyMessage, final BaseServerHumanPlayer pJoiningPlayer, final ClientMatchJoinLeaveRequestMessage pClientMessage);
	
	/**
	 * In this method you should announce a slot change which happens to the
	 * slot shipped by the parameters. You should obtain an instance of your
	 * implementation of {@link AbsServerMatchpreparationSlotChangedAnnouncementMessage}
	 * from the server message pool and send this message to all joined players.
	 * ({@link #getJoinedPlayers()}). Do not forget to recycle this message 
	 * afterwards.
	 * 
	 * @param pChangedSlot - The slot that was changed.
	 */
	protected abstract void announceSlotChanged(final ServerPlayerSlotClass pChangedSlot);
	
	/**
	 * In this method you should create a match object on the basis of this
	 * match preparation object.
	 * <p>
	 * Manual:<br>
	 * At first you should check if the match is in the state {@link MatchPreparationState#PREPARING}.
	 * Throw an {@link IllegalStateException} if that is not the case. Now you
	 * should change the state of this match preparation object to {@link MatchPreparationState#STARTING}. 
	 * Then you should remove this match preparation object from the pool of 
	 * active match preparation inside the game server ({@link AbsGameServer#getMatchIDsMatchPreparationsMap()).
	 * Afterwards you should create an instance of your implementation of
	 * {@link AbsServerMatch} and register it on the game server {{@link #mGameServer}.
	 * <p>
	 * Notice:<br>
	 * The location of this method may be changed.
	 * 
	 * @throws TMXLoadException
	 * @throws IOException
	 * @throws SAXException
	 */
	protected abstract void createMatch() throws TMXLoadException, IOException, SAXException;
	
	/**
	 * In this method you can initialize custom slot components (player colors 
	 * for example). You should avoid to initialize those components inside the 
	 * child constructor of your implementation of this class because the child
	 * constructor gets executed after the super-constructor which may call
	 * methods ({@link #createPlayerSlotsMap(int)} for example} which need
	 * your custom components.
	 * 
	 * @param pMap - An instance of your implementation of {@link AbsMapOverview}
	 * 				 which was used to initialize this match preparation object.
	 */
	protected abstract void initAdditionalSlotComponents(final MapOverviewClass pMap);
	
	/**
	 * This method should return a map of slot IDs -> player slots (instances of
	 * your implementation of {@link BaseServerPlayerSlot}. 
	 * <p>
	 * Notice:<br>
	 * The number of player slots its limited by the pPlayerCount parameter. You
	 * may want to shuffle the factions to set different default factions per slot.
	 *  
	 * @param pPlayerCount - The maximum number of player slots.
	 * @param pFactions - All available factions.
	 * @param pTeams - All available teams.
	 * 
	 * @return
	 */
	protected abstract HashMap<Long, ServerPlayerSlotClass> createPlayerSlotsMap(final int pPlayerCount, final Collection<Team> pTeams, final HashSet<BaseFactionClass> pFactions);
	
	/**
	 * This method should return a set of all factions which are available
	 * to the players.
	 * 
	 * @param pContext
	 * 
	 * @return a set of all factions which are available to the players.
	 */
	protected abstract HashSet<BaseFactionClass> createFactions(final Context pContext);
	
	/**
	 * This method sends an announcement to all joined human players if a player
	 * joins or leaves the match preparation.
	 *  
	 * @param pAffectedPlayer - The affected player.
	 * @param pAnnouncementType - The type of the announcement (join or leave).
	 */
	protected void announcePlayerJoinedOrLeftMatch(final BasePlayer pAffectedPlayer, final PlayerAnnouncementType pAnnouncementType) {
		
		final MessagePool<IMessage> serverMessagePool = this.mGameServer.getServerMessagePool();
		
		final ServerMatchPlayerJoinedLeftAnnouncementMessage announcement = (ServerMatchPlayerJoinedLeftAnnouncementMessage) serverMessagePool.obtainMessage(FLAG_SERVER_MATCH_PLAYER_JOINED_LEFT_ANNOUNCEMENT);
		
		announcement.setPlayer(pAffectedPlayer);
		announcement.setActionType(pAnnouncementType);
		announcement.setMatchPreparation(this);
		
		for(BasePlayer currentPlayer : this.mJoinedPlayerIDsPlayersMap.values()){
					
			// Wenn es sich um einen menschlichen Spieler handelt
			if(currentPlayer instanceof BaseServerHumanPlayer){
				
				try {
					((BaseServerHumanPlayer) currentPlayer).getClientConnector().sendServerMessage(announcement);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		serverMessagePool.recycleMessage(announcement);
	}
	
	
	/**
	 * This method processes an incoming match start request. The only player
	 * who can request a match start is the match creator. Also, all players
	 * have to be ready to start the match.
	 * 
	 * @param pMessage - The incoming {@link ClientStartMatchRequestMessage}.
	 * @param pReply - The reply message which will be send to the player.
	 * @param player - The requesting player.
	 */
	public void processMatchStartRequestMessage(final ClientStartMatchRequestMessage pMessage, final ServerMatchpreprataionStartRequestReplyMessage pReply, final BaseServerHumanPlayer player) {
		
		if(this.mMatchCreator.equals(player)){

			if(this.mPreparedPlayers.containsAll(this.getJoinedPlayers())){
				
				pReply.setAccepted(true);
				pReply.setReason(StartReplyReason.OK);
				
				try {
					this.createMatch();
				} catch (TMXLoadException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (SAXException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}else{
				
				pReply.setAccepted(false);
				pReply.setReason(StartReplyReason.PLAYER_NOT_READY);
			}
		}
	}
	
	/**
	 * This method toggles the sending players ready/unready state. 
	 * 
	 * @param pMessage - The incoming {@link ClientToggleReadyToPlayMessage}.
	 * @param pPlayer - The sending player.
	 */
	public void processPlayerToggleReadyToPlay(final ClientToggleReadyToPlayMessage pMessage, final BaseServerHumanPlayer pPlayer) {
		
		if(this.mJoinedPlayerIDsPlayersMap.values().contains(pPlayer) && !this.mBannedPlayersSet.contains(pPlayer)){
		
			if(pMessage.isReady()){
				
				this.mPreparedPlayers.add(pPlayer);
				
			}else{
				
				this.mPreparedPlayers.remove(pPlayer);
			}
		}
	}
		
	private HashMap<Integer,Team> createTeamsMap(final int pPlayerCount) {
		
		final HashMap<Integer,Team> teamsMap = new HashMap<Integer,Team>();
		
		for(int i = 0; i < pPlayerCount; i++){
			
			final int teamId = i + 1;
			
			teamsMap.put(teamId, new Team(teamId));
		}
				
		return teamsMap;
	}
	
	
	/**
	 * @return the mMap
	 */
	public MapOverviewClass getMapOverview() {
		return mMapOverview;
	}

	/**
	 * @return the mMatchCreator
	 */
	public BasePlayer getMatchCreator() {
		return mMatchCreator;
	}

	/**
	 * @return the mMatchID
	 */
	public UUID getMatchID() {
		return mMatchID;
	}

	/**
	 * @return the mMatchMode
	 */
	public MatchPreparationState getMatchState() {
		return mMatchPreparationState;
	}

	/**
	 * This method return the number of open slots of this match preparation.
	 * 
	 * @return the number of open slots.
	 */
	public int getOpenSlotsCount(){
		
		int availableSlotsCount = 0;
		
		for(ServerPlayerSlotClass currentSlot : this.mSlotIDsPlayerSlots.values()){
			
			if(currentSlot.getPlayerSlotState() == SlotState.OPEN){
				
				availableSlotsCount++;
			}
		}
		
		return availableSlotsCount;
	}

	/**
	 * @return the mBannedPlayersSet
	 */
	public Set<BaseHumanPlayer> getBannedPlayersSet() {
		return mBannedPlayersSet;
	}
	
	/**
	 * This method processes a slot team change request. It also announces the
	 * slot change to all joined players. It returns true if the change was
	 * feasible. 
	 * 
	 * @param pAffectedSlot - The slot which should be changed.
	 * @param pNewTeamID - The new team ID for the slot.
	 * 
	 * @return true if the change was feasible, false otherwise. 
	 */
	protected boolean processSlotChangeTeam(final ServerPlayerSlotClass pAffectedSlot, final int pNewTeamID) {
		
		if(this.mTeamIDsTeamsMap.containsKey(pNewTeamID)){
		
			pAffectedSlot.setTeam(this.mTeamIDsTeamsMap.get(pNewTeamID));
			
			this.announceSlotChanged(pAffectedSlot);
			
			return true;
			
		}else{
			
			return false;
		}
	}

	/**
	 * This method processes a slot faction change request. It also announces the
	 * slot change to all joined players. It returns true if the change was
	 * feasible. 
	 * 
	 * @param pAffectedSlot - The slot which should be changed.
	 * @param pNewFaction - The new faction.
	 * 
	 * @return true if the change was feasible, false otherwise. 
	 */
	protected boolean processSlotChangeFaction(final ServerPlayerSlotClass pAffectedSlot, final BaseFactionClass pNewFaction) {
		
		pAffectedSlot.setFaction(pNewFaction);
		
		this.announceSlotChanged(pAffectedSlot);
		
		// TODO: Implement a way to restrict factions according to map flags.
		
		return true;
	}

	/**
	 * This method processes a slot player change request. It also announces the
	 * slot change to all joined players. It returns true if the change was
	 * feasible. 
	 * 
	 * @param pAffectedSlot - The slot which should be changed.
	 * @param pNewPlayer - The new player.
	 * 
	 * @return true if the change was feasible, false otherwise. 
	 */
	protected boolean processSlotChangePlayer(final ServerPlayerSlotClass pAffectedSlot,
			BasePlayer pNewPlayer) {
		
		ServerPlayerSlotClass oldSlot = null;
		
		for(ServerPlayerSlotClass currentSlot : this.mSlotIDsPlayerSlots.values()){
			
			if(currentSlot.hasPlayer() && currentSlot.getPlayer().equals(pNewPlayer)){
				
				oldSlot = currentSlot;
				break;
			}
		}
				
		// If there is an old slot
		if(oldSlot != null){
		
			// If it is the same slot (same Slot ID)
			if(oldSlot.equals(pAffectedSlot)){
				
				return true;
			
			// If it is a different slot
			}else{
				
				if(pAffectedSlot.hasPlayer()){
					
					oldSlot.setPlayer(pAffectedSlot.getPlayer());
										
				}else{
					
					oldSlot.setSlotState(SlotState.OPEN);
				}
				
				pAffectedSlot.setPlayer(pNewPlayer);
								
				this.announceSlotChanged(oldSlot);

				this.announceSlotChanged(pAffectedSlot);
								
				return true;
			}
			
		}else{
			
			pAffectedSlot.setPlayer(pNewPlayer);
			
			this.announceSlotChanged(pAffectedSlot);
			
			return true;
		}
	}

	/**
	 * This method processes a slot state change request. It also announces the
	 * slot change to all joined players. It returns true if the change was
	 * feasible. 
	 * 
	 * @param pAffectedSlot - The slot which should be changed.
	 * @param pNewState - The new state.
	 * 
	 * @return true if the change was feasible, false otherwise. 
	 */
	protected void processSlotChangeState(final ServerPlayerSlotClass pAffectedSlot,
			SlotState pNewState) {
		
		if(!pAffectedSlot.hasPlayer()){
			
			pAffectedSlot.setSlotState(pNewState);
								
		}else{
			
			if(pAffectedSlot.getPlayer() instanceof BaseServerHumanPlayer){
				
				this.banPlayer((BaseServerHumanPlayer) pAffectedSlot.getPlayer());
			}
						
			pAffectedSlot.removePlayer();
			
			pAffectedSlot.setSlotState(pNewState);
		}
		
		this.announceSlotChanged(pAffectedSlot);
	}
		
	/**
	 * This method joins a player. Therefore it searches for a free slot
	 * an puts the player into it. It also registers in the {@link #mJoinedPlayerIDsPlayersMap}
	 * and announces the joining player and the slot change.
	 * 
	 * @param pJoiningPlayer - The joining player.
	 */
	protected void joinPlayer(final BasePlayer pJoiningPlayer) {
		
		this.mJoinedPlayerIDsPlayersMap.put(pJoiningPlayer.getPlayerID(), pJoiningPlayer);
		
		for(ServerPlayerSlotClass currentSlot : this.mSlotIDsPlayerSlots.values()){
			
			if(currentSlot.getPlayerSlotState() == SlotState.OPEN){
		
				currentSlot.setPlayer(pJoiningPlayer);
				
				this.announcePlayerJoinedOrLeftMatch(pJoiningPlayer, PlayerAnnouncementType.JOINED);
				this.announceSlotChanged(currentSlot);
				
				return;
			}
		}
		
		// If theire is no free slot for any reason...
		throw new IllegalStateException("No Slot available");
	}
	
	/**
	 * This method removes a player from the match preparation and announces
	 * this event to all other players.
	 * 
	 * @param pAffectedPlayer
	 */
	public void removePlayer(final BasePlayer pAffectedPlayer){
		
		this.mJoinedPlayerIDsPlayersMap.remove(pAffectedPlayer.getPlayerID());
		
		this.mPreparedPlayers.remove(pAffectedPlayer);
		
		for(ServerPlayerSlotClass currentSlot : this.mSlotIDsPlayerSlots.values()){
			
			if(currentSlot.getPlayer().equals(pAffectedPlayer)){
		
				currentSlot.setSlotState(SlotState.OPEN);
				
				this.announcePlayerJoinedOrLeftMatch(pAffectedPlayer, PlayerAnnouncementType.LEFT);
				this.announceSlotChanged(currentSlot);
				
				return;
			}
		}
	}
	
	/**
	 * This method bans a player from the match preparation and announces this
	 * event to all other players.
	 * 
	 * @param pPlayer
	 */
	protected void banPlayer(final BaseServerHumanPlayer pPlayer){
		
		this.mBannedPlayersSet.add(pPlayer);
				
		this.announcePlayerJoinedOrLeftMatch(pPlayer, PlayerAnnouncementType.BANNED);
		
		this.mJoinedPlayerIDsPlayersMap.remove(pPlayer.getPlayerID());
	}

	/**
	 * This method cancels the match preparation. It will also announce
	 * this event to all joined players and removes itself from the game server.
	 */
	public void cancelMatch(){
		
		if(this.mMatchPreparationState != MatchPreparationState.PREPARING){
			
			throw new IllegalStateException("The match preparation has to be in the state PREPARING to cancel it. Current State: "+this.mMatchPreparationState.name());
		}
		
		this.mMatchPreparationState = MatchPreparationState.CANCELED;
		
		this.announceMatchStateChanged();
		
		this.mGameServer.getMatchIDsMatchPreparationsMap().remove(this.mMatchID);
	}

	/**
	 * This method sends an match preparation state change to all joined players.
	 */
	protected void announceMatchStateChanged() {
		
		final MessagePool<IMessage> serverMessagePool = this.mGameServer.getServerMessagePool();
		
		final ServerMatchpreprataionStateChangedAnnouncement announcement = (ServerMatchpreprataionStateChangedAnnouncement) serverMessagePool.obtainMessage(FLAG_SERVER_MATCHPREPARATION_STATE_CHANGED_ANNOUNCEMENT);
		
		announcement.setMatchPreparation(this);
		
		for(BasePlayer player : this.mJoinedPlayerIDsPlayersMap.values()){
			
			if((player instanceof BaseServerHumanPlayer) && (!player.equals(this.mMatchCreator))){
				
				try {
					((BaseServerHumanPlayer)player).getClientConnector().sendServerMessage(announcement);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		serverMessagePool.recycleMessage(announcement);
	}
	
	
	/**
	 * @return the mPlayerSlots
	 */
	public Collection<ServerPlayerSlotClass> getPlayerSlots() {
		return mSlotIDsPlayerSlots.values();
	}

	/**
	 * @return the mJoinedPlayersSet
	 */
	public Collection<BasePlayer> getJoinedPlayers() {
		return mJoinedPlayerIDsPlayersMap.values();
	};
	
	/**
	 * Two match preparation objects are the same if both have the same match ID.
	 */
	@Override
	public int compareTo(AbsServerMatchPreparation another) {

		if(this.getMatchID().equals(another.getMatchID())){
			
			return 0;
		
		}else{
			
			return 1;
		}
	}
	
	/**
	 * Two match preparation objects are equal if they are of the same base type
	 * ({@link AbsServerMatchPreparation}) and share the same match ID.
	 */
	@Override
	public boolean equals(Object o) {
		
		if(o instanceof AbsServerMatchPreparation){
			
			return (this.mMatchID.equals(((AbsServerMatchPreparation) o).getMatchID()));
		
		}else{
		
			return super.equals(o);
		}
	}

	/**
	 * This method checks if a given player is a member of this match. 
	 * 
	 * @param pPlayer
	 * 
	 * @return
	 */
	public boolean isPlayerMemberOfMatch(final BasePlayer pPlayer){
		
		return this.mJoinedPlayerIDsPlayersMap.values().contains(pPlayer);
	}



	/**
	 * @return the mJoinedPlayerIDsPlayersMap
	 */
	public Map<UUID, BasePlayer> getJoinedPlayerIDsPlayersMap() {
		return mJoinedPlayerIDsPlayersMap;
	}

	/**
	 * @return the mTeamIDsTeamsMap
	 */
	public Map<Integer, Team> getTeamIDsTeamsMap() {
		return mTeamIDsTeamsMap;
	}
}
